import DebugExt from "../base/DebugExt";

const { ccclass, property, requireComponent, menu } = cc._decorator;
/** 特殊动作标识 */
const ACTION_TAG = 1989;
/** 特定事件名，解析为动画对象 */
class EventNameData {
    /** 事件名 */
    name: string = "";

    /** 动作名称 */
    act: string = "";
    /** 动作持续帧数 */
    frame: string = "";
    /** 动作对象名 */
    target: string = "";

    /** 动画持续时间 */
    time: number = 0;

    constructor(eventName: string, fps: number = 30) {
        if (!eventName) return;
        this.name = eventName.trim();
        if (!this.name) return;
        const eventActs: string[] = this.name.split('_');
        this.act = eventActs[0];
        this.frame = eventActs[1];
        this.target = eventActs[2] || '';
        this.time = !this.frame ? 0 : parseInt(this.frame) / fps;
    }
}

@ccclass("SpineBoneData")
export class SpineBoneData {

    /** 待关联骨骼动画骨骼名 */
    @property({
        tooltip: "待关联骨骼动画骨骼名"
    })
    public boneName: string = '';

    /** 关联节点 */
    @property({
        type: cc.Node,
        tooltip: "关联节点"
    })
    public bindNode: cc.Node = null;


    /** 解析获取的骨骼相关数据 */
    private _boneData: any = null;

    /** 初始化骨骼信息 */
    public initBoneData(spine: sp.Skeleton) {
        if (!spine || !this.boneName) return;
        this._boneData = spine.findBone(this.boneName);
        // console.log("测试数据：", this.boneName, "\n ", this._boneData);
    }

    /** 更新绑定节点属性 */
    public update() {
        if (!this._boneData || !this.bindNode || !this.bindNode.isValid) return;
        this.bindNode.setPosition(this._boneData.worldX, this._boneData.worldY);
        this.bindNode.setScale(this._boneData.scaleX, this._boneData.scaleY);
        // DebugExt.log("测试信息：\n", this.boneName, " 位置：(", this._boneData.worldX, ",", this._boneData.worldY,
        //     ")\n 缩放: (", this._boneData.scaleX, ",", this._boneData.scaleY, ")");
    }
}


/**
 * 尝试实现V2.1.2引擎下骨骼动画挂点
 */
@ccclass
@requireComponent(sp.Skeleton)
@menu('SpineExtra/SpineHockBone')
export default class SpineHockBone extends cc.Component {

    @property([SpineBoneData])
    private readonly datas: SpineBoneData[] = [];

    @property({
        tooltip: "帧率;默认每秒30帧"
    })
    private readonly fps: number = 30;


    /** 是否需要更新绑定对象 */
    private _updateBind = false;
    /** in动画结束回调--依赖于spine动画事件，不一定存在 */
    public set showComplete(func: () => void) {
        this._showComplete = func;
    }
    private _showComplete: () => void = null;
    /** out动画开始回调--依赖于spine动画事件，不一定存在 */
    public set hideStart(func: () => void) {
        this._hideStart = func;
    }
    private _hideStart: () => void = null;
    /** spine动画结束回调 */
    public set hideComplete(func: () => void) {
        this._hideComplete = func;
    }
    private _hideComplete: () => void = null;

    // LIFE-CYCLE CALLBACKS:

    /** 骨骼动画对象 */
    private get spine(): sp.Skeleton {
        return this.getComponent(sp.Skeleton);
    }

    /** 播放骨骼动画 */
    public play(name: string = "", trackIndex: number = 0, loop: boolean = false) {
        if (!this.spine) return;
        if (!name) name = this.spine.defaultAnimation;
        if (!name) {
            DebugExt.warn(this.spine.name, "无默认动画!");
            return;
        }
        this.prepareOpacity();
        this.spine.node.active = true;
        this.spine.clearTracks();
        if (this.datas.length > 0) {
            this.spine.updateWorldTransform();
            for (const info of this.datas) {
                if (info) info.initBoneData(this.spine);
            }
            this._updateBind = true;
        }
        this.spine.setEventListener((track: any, event: any) => {
            DebugExt.log("骨骼动画帧事件", event);
            const data = event ? event.data : null;
            if (!data || !(data.name as string)) return;
            this.playEventAnimation((data.name as string).trim());
        });
        this.spine.setCompleteListener(() => {
            DebugExt.log("骨骼动画完成一次循环事件");
            this._updateBind = false;
            if (!loop) {
                this.spine.node.active = false;
                if (this._hideComplete)
                    this._hideComplete();
            }
        });
        this.spine.timeScale = this._timeScale;
        this.spine.setAnimation(trackIndex, name, loop);
    }

    /** 当前骨骼中所有动画的时间缩放率 */
    public set speed(num: number) {
        this._timeScale = num;
        this.spine.timeScale = num;
    }
    private _timeScale: number = 1;

    protected update(dt: number): void {
        if (!this._updateBind || !this.spine) return;
        this.spine.updateWorldTransform();
        if (this.datas.length > 0) {
            for (const info of this.datas) {
                if (info) info.update();
            }
        }
    }

    // 执行骨骼动画约定事件(目前仅支持-淡入、淡出)
    private playEventAnimation(eventName: string) {
        if (!eventName || this.datas.length == 0) return;
        const eventData: EventNameData = new EventNameData(eventName, this.fps);
        const tarNodes: cc.Node[] = this.getBindNodes(eventData.target);
        if (!tarNodes || tarNodes.length == 0) return;
        const time: number = eventData.time * this._timeScale;
        for (const node of tarNodes) {
            if (!node || !node.isValid) continue;
            if (eventData.act == "in") {
                node.stopActionByTag(ACTION_TAG);
                const act = node.runAction(cc.sequence(
                    cc.fadeIn(time),
                    cc.callFunc(() => {
                        if (this._showComplete)
                            this._showComplete();
                    })
                ));
                act.setTag(ACTION_TAG);
            }
            else if (eventData.act == "out") {
                node.stopActionByTag(ACTION_TAG);
                const act = node.runAction(cc.fadeOut(time));
                act.setTag(ACTION_TAG);
                if (this._hideStart)
                    this._hideStart();
            }
        }
    }
    // 按照指定插槽名称，查找绑定的对象节点
    private getBindNodes(key: string): cc.Node[] {
        if (this.datas.length == 0)
            return null;
        let ary: cc.Node[] = [];
        for (const data of this.datas) {
            if (!key || key == data.boneName)
                ary.push(data.bindNode);
        }
        return ary;
    }
    // 预处理淡入动画
    private prepareOpacity() {
        if (!this.spine) return;
        const spData = (this.spine.skeletonData as any)._skeletonCache;
        if (!spData) return;
        const events: any[] = spData.events;
        if (!events || events.length == 0) return;
        let targets: string[] = [];
        let nodes: cc.Node[] = [];
        for (const event of events) {
            if (!event || !event.name) continue;
            const name: string = event.name as string;
            if (!name || name.indexOf("in") != 0) continue;
            const data: EventNameData = new EventNameData(name, 0);
            if (targets.indexOf(data.target) >= 0) continue;
            targets.push(data.target);
            const tarNodes: cc.Node[] = this.getBindNodes(data.target);
            if (!tarNodes || tarNodes.length == 0) return;
            tarNodes.forEach(node => nodes.push(node));
        }
        if (nodes.length == 0) return;
        for (const node of nodes) {
            if (!node || !node.isValid) continue;
            node.opacity = 0;
            node.active = true;
        }
    }

}
